package net.ib.mtalk.ui.attachment.scribble;

import java.util.NoSuchElementException;

import net.ib.mtalk.util.Dip;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

class ScribbleView extends View {

	/** 지우개 굵기 */
	public float eraserThickness = Dip.toPixel(30);
	/** 획의 굵기 */
	public float penThickness = Dip.toPixel(3);

	/** 배경 */
	public int baseColor = Color.WHITE;
	private Bitmap backgroundBitmap = null;
	public void setBackgroundBitmap(Bitmap background) {
		backgroundBitmap = background;
	}
	public Bitmap getBackgroundBitmap() {
		return backgroundBitmap;
	}

	/** 화면 버퍼의 페인트. 화면 캐시에도 사용된다. */
	private Paint screenPaint = new Paint(Paint.DITHER_FLAG);

	/** 현재 화면에 대한 캐시 */
	private Bitmap currentShotBitmap = null;
	public Bitmap getCurrentBitmap() {
		return currentShotBitmap;
	}
	/** 현재 화면에 대한 캔버스 */
	private Canvas currentShotCanvas = null;
	/**
	 * 비트맵과 캔버스 크기에 대한 lazy evaluation(?).
	 * 이것은 그리기 도구들에 대한 초기화인데, 화면에 한 번 그려진 후에 수행되어야 한다.
	 */
	protected void initialize() {
		currentShotBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		currentShotCanvas = new Canvas(currentShotBitmap);

		strokeBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		strokeCanvas = new Canvas(strokeBitmap);
	}

	/** 획들에 대한 캐시(배경 제외) */
	private Bitmap strokeBitmap = null;
	/** 획들에 대한 캔버스 */
	private Canvas strokeCanvas = null;
	/** 획에 대한 페인트 */
	private Paint strokePaint = new Paint();
	{	// 객체 초기화 블럭
		strokePaint.setAntiAlias(true);
		strokePaint.setDither(true);
		strokePaint.setStyle(Paint.Style.STROKE);
		strokePaint.setStrokeJoin(Paint.Join.ROUND);
		strokePaint.setStrokeCap(Paint.Cap.ROUND);
		strokePaint.setStrokeWidth(penThickness);
		strokePaint.setColor(Color.BLACK);
	}
	public int getStrokeColor() {
		return strokePaint.getColor();
	}
	public void setStrokeColor(int color) {
		strokePaint.setColor(color);
	}
	/** 획의 경로 */
	private Path strokePath = new Path();

	/** 페인트와 획에 대한 관리자. */
	private ScribbleManager history = new ScribbleManager();



	public ScribbleView(Context context) {
		super(context);
	}

	public ScribbleView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public ScribbleView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int wMode, hMode;
		int wSpec, hSpec;
		int width, height;

		width = 150;
		wMode = MeasureSpec.getMode(widthMeasureSpec);
		wSpec = MeasureSpec.getSize(widthMeasureSpec);
		switch (wMode) {
		case MeasureSpec.AT_MOST:
			width = Math.min(wSpec, width);
			break;
		case MeasureSpec.EXACTLY:
			width = wSpec;
			break;
		case MeasureSpec.UNSPECIFIED:
			break;
		}

		height = 80;
		hMode = MeasureSpec.getMode(heightMeasureSpec);
		hSpec = MeasureSpec.getSize(heightMeasureSpec);
		switch (hMode) {
		case MeasureSpec.AT_MOST:
			height = Math.min(hSpec, height);
			break;
		case MeasureSpec.EXACTLY:
			height = hSpec;
			break;
		case MeasureSpec.UNSPECIFIED:
			break;
		}

		setMeasuredDimension(width, height);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (currentShotBitmap == null) {	// 이 조건을 무엇으로 하는 게 좋을지 정확히 모르겠다. 주의.
			initialize();
		}

		if (! history.isDrawn()) {	// 스택의 갱신을 체크, 갱신되었다면 현재 화면 캐시를 재구축해야 한다.
			// 배경 -> 현재 화면 캐시
			if (backgroundBitmap != null) {
				currentShotCanvas.drawBitmap(backgroundBitmap, null, new Rect(0, 0, canvas.getWidth(), canvas.getHeight()), screenPaint);
			} else {
				currentShotCanvas.drawColor(baseColor);
			}
			// 획 내역 재구성
			strokeCanvas.drawColor(baseColor, PorterDuff.Mode.CLEAR);
			history.draw(strokeCanvas);
			// 재구성한 획 내역 -> 현재 화면 캐시
			currentShotCanvas.drawBitmap(strokeBitmap, 0, 0, screenPaint);
		}
		canvas.drawBitmap(currentShotBitmap, 0, 0, screenPaint);	// 현재 화면 캐시를 그대로 그려줌
		canvas.drawPath(strokePath, strokePaint);	// 터치 업이 아직 이루어지지 않은, 그리는 중인 획을 보여준다.
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float x = event.getX();
		float y = event.getY();

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			touchStart(x, y);
			invalidate();
			break;
		case MotionEvent.ACTION_MOVE:
			touchMove(x, y);
			invalidate();
			break;
		case MotionEvent.ACTION_UP:
			touchUp();
			invalidate();
			break;
		}
		return true;
	}

	protected float mX, mY;
	protected static final float TOUCH_TOLERANCE = Dip.toPixel(3);

	protected void touchStart(float x, float y) {
		strokePath.reset();
		strokePath.moveTo(x, y);
		mX = x;
		mY = y;
	}

	protected void touchMove(float x, float y) {
		float dx = Math.abs(x - mX);
		float dy = Math.abs(y - mY);
		if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
			strokePath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
			mX = x;
			mY = y;
		}
	}

	protected void touchUp() {
		strokePath.lineTo(mX, mY);
		// commit the path to scribble history
		history.add(strokePath, strokePaint);
		// kill this so we don't double draw
		strokePath.reset();
	}

	public int getNumberOfStroke() {
		return history.getSize();
	}

	/**
	 * 획의 페인트를 그리기를 위한 상태로 만든다.
	 */
	public void changeModeDraw() {
		strokePaint.setXfermode(null);
		strokePaint.setAlpha(0xFF);
		strokePaint.setStrokeWidth(penThickness);
	}

	/**
	 * 획의 페인트를 지우개를 위한 상태로 만든다.
	 */
	public void changeModeErase() {
		strokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
		strokePaint.setStrokeWidth(eraserThickness);
	}

	/**
	 * undo one step
	 */
	public void removeStroke() {
		try{
			history.remove();
			invalidate();
		}catch(NoSuchElementException e){
			// do nothing
		}
	}

	/**
	 * redo one step
	 */
	public void recoverStroke(){
		try{
			history.recover();
			invalidate();
		}catch(NoSuchElementException e){
			// do nothing
		}
	}

}
